#include <string.h>
#include <stdlib.h>
#include "EditStack.h"
#include "TextDoc.h"
#include <assert.h>

CEditOperation::CEditOperation()
{
    m_startline = 0;
    m_startcol = 0;
    m_endline = 0;
    m_endcol = 0;

    m_text = NULL;
    m_operation = NotDefined;
}

CEditOperation::~CEditOperation()
{
    if (m_text) {
        free (m_text);
    }
}

int CEditOperation::CopyString(const char* src)
{
    if (m_text) {
        free (m_text);
    }
    m_text = (char*) malloc (strlen(src) + 1);
    if (!m_text) {
        return -1;
    }
    strcpy (m_text, src);
    return 0;
}

/////////////////////////////////////////////////////

#define CIRC_INC(x,limit)  \
    do {    \
        (x) = ((x) >= (limit)-1 ? 0 : (x)+1);   \
    } while (0)
                    
#define CIRC_DEC(x,limit) \
    do {    \
        (x) = ((x) == 0 ? (limit)-1 : (x)-1);    \
    } while (0)

CEditStack::CEditStack()
{
    m_doc = NULL;
    m_circqueue = NULL;
    m_uCount = 0;
    m_uStart = 0;
    m_uEnd = 0;
    m_uCurPos = 0;
    m_bEmpty = true;
    m_uOperationID = 0;
    m_bInTransaction = false;
    m_bHitStart = true;
}

CEditStack::~CEditStack()
{
    ClearAll();
    if (m_circqueue) {
        delete m_circqueue;
    }
}

int CEditStack::SetStackSize(unsigned int nCount)
{
    assert (m_uCount == 0);
    assert (m_circqueue == NULL);
    assert (nCount > 0);

    m_circqueue = (CEditOperation**) malloc (sizeof(CEditOperation*) * nCount );
    memset(m_circqueue, 0, sizeof(CEditOperation*) * nCount);
    if (!m_circqueue) {
        return -1;
    }
    m_uCount = nCount;
    return 0;
}

void CEditStack::SetDocument(CTextDoc* doc)
{
    assert (doc);
    m_doc = doc;
}

int CEditStack::AddOperation (CEditOperation* op)
{
    assert (op);

    MoveEndToCurPos();
    op->m_uID = m_uOperationID;
    if (m_uEnd != m_uStart || m_bEmpty) {
        m_circqueue[m_uEnd] = op;
    } else {
        RemoveOpsFromStart();
        m_circqueue[m_uEnd] = op;
    }
    
    if (m_bInTransaction && m_bFirstInTransaction || !m_bInTransaction ) {
        m_bFirstInTransaction = false;
    }
    m_uCurPos = m_uEnd;
    CIRC_INC(m_uEnd, m_uCount);
    m_uOperationID = (m_bInTransaction ? m_uOperationID : m_uOperationID+1);
    m_bEmpty = false;
    m_bHitStart = false;
    return 0;
}

void CEditStack::StartTransaction()
{
    m_bInTransaction = true;
    m_bFirstInTransaction = true;
}

void CEditStack::EndTransaction()
{
    m_bInTransaction = false;
    m_bFirstInTransaction = false;
    m_uOperationID++;
}

int CEditStack::MoveCurPosForward()
{
    unsigned int uPosToBe = m_uCurPos;
    unsigned int id = m_circqueue[uPosToBe]->m_uID;

    m_bHitStart = false;

    CIRC_INC(uPosToBe, m_uCount);
    while (uPosToBe != m_uEnd && m_circqueue[uPosToBe]->m_uID == id) {
        CIRC_INC(uPosToBe, m_uCount);
    }
    if (uPosToBe == m_uEnd) {
        return -1;
    }
    m_uCurPos = uPosToBe;
    return 0;
}

int CEditStack::MoveCurPosBackward()
{
    if (m_uCurPos == m_uStart) {
        m_bHitStart = true;
        return -1;
    }
    unsigned int id = m_circqueue[m_uCurPos]->m_uID;
    while (m_uCurPos != m_uStart && m_circqueue[m_uCurPos]->m_uID == id) {
        CIRC_DEC(m_uCurPos, m_uCount);
    }
    if (m_uCurPos == m_uStart && m_circqueue[m_uCurPos]->m_uID == id) {
        m_bHitStart = true;
        return -1;
    }
    return 0;
}

int CEditStack::ReDoCurOperations(unsigned int *rowCursor, unsigned int *colCursor)
{
    assert (m_doc);
    assert (rowCursor && colCursor);
    int ret = 0;

    if (m_bEmpty) {
        return -1;
    }

    if (!m_bHitStart) {
        ret = MoveCurPosForward();
        if (ret < 0) {
            return -1;
        }
    }
    unsigned int idx = (unsigned int) (-1);
    CEditOperation* op;

    while ((ret >= 0) &&(op = GetCurOperationsForward(&idx))) {
        ret = ReDoOperation(op);
        *rowCursor = op->m_startline;
        *colCursor = op->m_startcol;
        m_uCurPos = idx;
    }
    m_bHitStart = false;
    return ret;
}

int CEditStack::UnDoCurOperations(unsigned int *rowCursor, unsigned int *colCursor)
{
    assert (m_doc);

    if (m_bEmpty || m_bHitStart) {
        return -1;
    }

    unsigned int idx = (unsigned int) (-1);
    CEditOperation* op;
    int ret = 0;

    while ((ret >= 0) &&(op = GetCurOperationsBackward(&idx))) {
        ret = UnDoOperation(op);
        *rowCursor = op->m_startline;
        *colCursor = op->m_startcol;
        m_uCurPos = idx;
    }
    MoveCurPosBackward();
    return ret;
}

int CEditStack::ReDoOperation(CEditOperation* op)
{
    assert (op);
    assert (m_doc);
    
    int ret = 0;
    char* buf;
    switch (op->m_operation) {
    case Add:
        buf = (char*)malloc(strlen(op->m_text)+1);
        if (!buf) {
            return -1;
        }
        strcpy (buf, op->m_text);
        ret = m_doc->InsertTextAtByStack(op->m_startline, op->m_startcol, buf);
        free (buf);
        break;
    case Remove:
        m_doc->RemoveTextByStack(op->m_startline, op->m_startcol, op->m_endline, op->m_endcol);
        break;
    default:
        assert(0);
    }

    return ret;
}

int CEditStack::UnDoOperation(CEditOperation* op)
{
    assert (op);
    assert (m_doc);
    
    int ret = 0;
    char* buf;
    switch (op->m_operation) {
    case Remove:
        buf = (char*) malloc(strlen(op->m_text)+1);
        if (!buf) {
            return -1;
        }
        strcpy (buf, op->m_text);
        ret = m_doc->InsertTextAtByStack(op->m_startline, op->m_startcol, buf);
        free (buf);
        break;
    case Add:
        m_doc->RemoveTextByStack(op->m_startline, op->m_startcol, op->m_endline, op->m_endcol);
        break;
    default:
        assert(0);
    }

    return ret;
}

void CEditStack::ClearAll()
{
    if (!m_bEmpty && m_circqueue) {
        unsigned int i = m_uStart;
        while (i != m_uEnd) {
            assert (m_circqueue[i]);
            delete (m_circqueue[i]);
            CIRC_INC(i, m_uCount);
        }
    }
    m_uStart = 0;
    m_uEnd = 0;
    m_uCurPos = 0;
    m_bEmpty = true;
    m_uOperationID = 0;
    m_bInTransaction = false;
    m_bHitStart = true;
}

CEditOperation* CEditStack::GetCurOperationsForward(unsigned int* last_idx)
{
    assert (m_circqueue);
    if (*last_idx == (unsigned int)(-1)) {
        *last_idx = m_uCurPos;
        return m_circqueue[m_uCurPos];
    }

    unsigned int id = m_circqueue[m_uCurPos]->m_uID;
    CIRC_INC(*last_idx, m_uCount);
    if (*last_idx == m_uEnd || m_circqueue[*last_idx]->m_uID != id) {
        return NULL;
    }
    return m_circqueue[*last_idx];
}

CEditOperation* CEditStack::GetCurOperationsBackward(unsigned int* last_idx)
{
    assert (m_circqueue);
    if (*last_idx == (unsigned int)(-1)) {
        *last_idx = m_uCurPos;
        return m_circqueue[m_uCurPos];
    }

    unsigned int id = m_circqueue[m_uCurPos]->m_uID;

    if (*last_idx == m_uStart) {
        return NULL;
    }
    CIRC_DEC(*last_idx, m_uCount); 
    if (m_circqueue[*last_idx]->m_uID != id) {
        return NULL;
    }
    return m_circqueue[*last_idx];
}

void CEditStack::RemoveOpsFromStart()
{
    assert (!m_bEmpty);
    assert (m_circqueue);
    unsigned int id = m_circqueue[m_uStart]->m_uID;
    do {
        DeleteOpAt(m_uStart);
        CIRC_INC(m_uStart, m_uCount);
    } while (m_uStart != m_uEnd && (m_circqueue[m_uStart]->m_uID == id));
}

void CEditStack::MoveEndToCurPos()
{
    assert (m_circqueue);
    if (m_bEmpty) {
        return;
    }

    if (m_bHitStart) {
        ClearAll();
        return;
    }
    unsigned int i = m_uCurPos;
    unsigned int id = m_circqueue[i]->m_uID;
    do {
        CIRC_INC (i, m_uCount);
    } while ( i != m_uEnd && (m_circqueue[i]->m_uID == id));
    unsigned int uEndToBe = i;

    while (i != m_uEnd) {
        DeleteOpAt(i);
        CIRC_INC(i, m_uCount);
    } 
    m_uEnd = uEndToBe;
}

void CEditStack::DeleteOpAt(unsigned int idx)
{
    assert (m_circqueue);
    assert (m_circqueue[idx]);
    delete (m_circqueue[idx]);
    m_circqueue[idx] = NULL;
}
